// -----------------------------------------------------------------------------
// M A R I T I M E   R E S E A R C H   I N S T I T U T E   N E T H E R L A N D S
// -----------------------------------------------------------------------------
// Copyright (C) 2009 MARIN - All rights reserved - http://www.marin.nl
// -----------------------------------------------------------------------------
// Program  : mermaid
// Module   : vbm
// File     : ShipGeometry.cpp
// -----------------------------------------------------------------------------

#include <new> // placement new
#include <fstream>
#ifndef _WINDOWS
#include "mermaid.h"
#endif
#include "defines.h"
#include <mml/Vector.h>
#include "ShipGeometry.h"
#include "assertplus.h"


#ifdef DEMO
#include <stdarg.h>
// provisional error handling
static void error(const char* a_format, ...)
{
    va_list ap;
    va_start(ap, a_format);
    vprintf(a_format, ap);
    va_end(ap);
    exit(0);
}
#else
static void error(const char*, ...)
{
}
#endif


// -----------------------------------------------------------------------------
// function ShipGeometry::ShipGeometry
// -----------------------------------------------------------------------------
/// constructor
// -----------------------------------------------------------------------------
ShipGeometry::ShipGeometry()
: super()
, m_appPanels()
, m_shipPanels()
, m_earthPanels()
, m_name("")
, m_beam(0.0)
, m_height(0.0)
, m_lpp(0.0)
, m_draftAft(0.0)
, m_draftFore(0.0)
, m_originApp(false)
{
}


// -----------------------------------------------------------------------------
// function ShipGeometry::~ShipGeometry()
// -----------------------------------------------------------------------------
/// destructor
// -----------------------------------------------------------------------------
ShipGeometry::~ShipGeometry()
{
}


// -----------------------------------------------------------------------------
// function ShipGeometry::importGeometry
// -----------------------------------------------------------------------------
/// imports data from mesh file and builds list of panels
/// taken from xxship::HullPanels::importGeometry
/// @return file read ok
// -----------------------------------------------------------------------------
bool ShipGeometry::read(std::string const& a_fileName) ///< name of msh file
{
    m_appPanels.clear();

    try
    {
        // open file
        std::ifstream file(a_fileName.c_str());

        // name of ship
        std::getline(file, m_name);

        // version (discarded)
        std::string version;
        std::getline(file, version);

        // Third line: lpp, b, draft aft, draft fore, height

        file >> m_lpp;
        if (m_lpp <= 0.0)
        {
            error("Lpp = %f, but should always be positive. Exiting ...", m_lpp);
            return false;
        }

        file >> m_beam;
        if (m_beam <= 0.0)
        {
            error("Beam = %f, but should always be positive. Exiting ...", m_beam);
            return false;
        }

        file >> m_draftAft;
        if (m_draftAft <= 0.0)
        {
            error("Ta = %f, but should always be positive. Exiting ...", m_draftAft);
            return false;
        }

        file >> m_draftFore;
        if (m_draftFore <= 0.0)
        {
            error("Tf = %f, but should always be positive. Exiting ...", m_draftFore);
            return false;
        }

        file >> m_height;
        if (m_height <= 0.0)
        {
            error("Height = %f, but should always be positive. Exiting ...", m_height);
            return false;
        }

        // Fourth line: nr of hull segments, nr of deck segments, max contours, max points

        int nHullSegments;
        file >> nHullSegments; // number of hull segments
        if (nHullSegments < 0)
        {
            error("# Hull segments = %d, but should always be non-negative. Exiting ...", nHullSegments);
            return false;
        }

        int nDeckSegments;
        file >> nDeckSegments; // number of deck segments
        if (nDeckSegments < 0)
        {
            error("# Deck segments = %d, but should always be non-negative. Exiting ...", nDeckSegments);
            return false;
        }

        int nMaxContourLines;
        file >> nMaxContourLines; // maximum number of contour lines in any segment (discarded)

        int nMaxVertices;
        file >> nMaxVertices; // maximum number of points on any contour line (discarded)

        // read all segments and transform into panels
        for (int i = 0; i < nHullSegments + nDeckSegments; ++i)
        {
            int nContours;
            file >> nContours; // number of contour lines in this segment
            if (nContours < 0)
            {
                error("# contours for segment %d = %d, but should always be non-negative. Exiting ...", i, nContours);
                return false;
            }

            int nVertices;
            file >> nVertices; // number of points on every contour line
            if (nVertices < 0)
            {
                error("# vertices for segment %d = %d, but should always be non-negative. Exiting ...", i, nVertices);
                return false;
            }

            int type;
            file >> type; // type of segment(hull, deck)
            if (type < 0)
            {
                error("type of segment %d = %d, but should always be non-negative. Exiting ...", i, type);
                return false;
            }

            // read all segment vertices
            std::vector<mml::Vector3d> vertices;
            for (int j = 0; j < nContours; ++j)
            {
                for (int k = 0; k < nVertices; ++k)
                {
                    double x, y, z;
                    file >> x;
                    file >> y;
                    file >> z;
                    vertices.push_back(mml::Vector3d(x, y, z));
                }
            }

            // create mesh
            for (int j = 0; j < nContours - 1; ++j)
            {
                for (int k = 0; k < nVertices - 1; ++k)
                {
                    mml::Vector3d v0 = vertices[ j * nVertices + k ];
                    mml::Vector3d v1 = vertices[(j + 1) * nVertices + k ];
                    mml::Vector3d v2 = vertices[(j + 1) * nVertices + k + 1];
                    mml::Vector3d v3 = vertices[ j * nVertices + k + 1];

                    // add vertices to panel which is added to panels if area > 0
                    xxship::Panel p;
                    p.addVertex(v3);
                    p.addVertex(v2);
                    p.addVertex(v1);
                    p.addVertex(v0);
                    p.flatten();
                    if (p.getArea() > 0.0)
                    {
                        m_appPanels.addPanel(p);

                        // negate y-components to create mirrored other half of ship
                        v0(1) = -v0(1);
                        v1(1) = -v1(1);
                        v2(1) = -v2(1);
                        v3(1) = -v3(1);

                        // add vertices to panel which is added to panels
                        // reverse order of vertices to point normal outward
                        p.clear();
                        p.addVertex(v0);
                        p.addVertex(v1);
                        p.addVertex(v2);
                        p.addVertex(v3);
                        p.flatten();
                        m_appPanels.addPanel(p);
                    }
                }
            }
        }

        translateAmidships();
        return true;
    }
    catch (...)
    {
        return false;
    }
}

// -----------------------------------------------------------------------------
// function ShipGeometry::getNumberOfPanels
// -----------------------------------------------------------------------------
/// @return number of panels
// -----------------------------------------------------------------------------
int ShipGeometry::getNumberOfPanels() const
{
    return getAppPanels().getNumberOfPanels();
}

// -----------------------------------------------------------------------------
// function ShipGeometry::getAppPanels
// -----------------------------------------------------------------------------
/// @return panels in ship coordinates wrt APP
// -----------------------------------------------------------------------------
const xxship::Panels& ShipGeometry::getAppPanels() const
{
    return m_appPanels;
}

// -----------------------------------------------------------------------------
// function ShipGeometry::getShipPanels
// -----------------------------------------------------------------------------
/// @return panels in ship coordinates wrt amidships
// -----------------------------------------------------------------------------
const xxship::Panels& ShipGeometry::getShipPanels() const
{
    return m_shipPanels;
}

// -----------------------------------------------------------------------------
// function ShipGeometry::getEarthPanels
// -----------------------------------------------------------------------------
/// @return panels in earth coordinates
// -----------------------------------------------------------------------------
const xxship::Panels& ShipGeometry::getEarthPanels() const
{
    return m_earthPanels;
}


// -----------------------------------------------------------------------------
// function ShipGeometry::getExtendedPanels
// -----------------------------------------------------------------------------
/// @return panels in earth coordinates with normals and centroids
// -----------------------------------------------------------------------------
void ShipGeometry::getExtendedPanels(std::vector<ShipGeometry::ExtendedPanel> *a_pPanels) const
{
    ASSERT(a_pPanels, "no panels");
    a_pPanels->clear();
    int n = m_earthPanels.getNumberOfPanels();
    for (int i = 0; i < n; ++i)
    {
        ShipGeometry::ExtendedPanel shipPanel;
        const xxship::Panel& panel = m_earthPanels.getPanel(i);
        int nn = panel.getNumberOfVertices();
        for (int j = 0; j < nn; ++j)
        {
            mml::Vector3d vertex = panel.getVertex(j);
            shipPanel.vertices.push_back(vertex);
        }
        shipPanel.normal = panel.getSurfaceVector();
        shipPanel.area = shipPanel.normal.norm();
        shipPanel.normal.normalize();
        shipPanel.centroid = panel.getCentroid();
        a_pPanels->push_back(shipPanel);
    }
}


// -----------------------------------------------------------------------------
// function ShipGeometry::getBeam()
// -----------------------------------------------------------------------------
/// @return witdh of ship
// -----------------------------------------------------------------------------
double ShipGeometry::getBeam() const
{
    return m_beam;
}


// -----------------------------------------------------------------------------
// function ShipGeometry::getHeight()
// -----------------------------------------------------------------------------
/// @return height of ship
// -----------------------------------------------------------------------------
double ShipGeometry::getHeight() const
{
    return m_height;
}

// -----------------------------------------------------------------------------
// function ShipGeometry::getLPP()
// -----------------------------------------------------------------------------
/// @return length over particulars
// -----------------------------------------------------------------------------
double ShipGeometry::getLPP() const
{
    return m_lpp;
}

// -----------------------------------------------------------------------------
// function ShipGeometry::getDrafAft()
// -----------------------------------------------------------------------------
/// @return draft aft
// -----------------------------------------------------------------------------
double ShipGeometry::getDrafAft() const
{
    return m_draftAft;
}

// -----------------------------------------------------------------------------
// function ShipGeometry::getDraftFore()
// -----------------------------------------------------------------------------
/// @return draft fore
// -----------------------------------------------------------------------------
double ShipGeometry::getDraftFore() const
{
    return m_draftFore;
}

// -----------------------------------------------------------------------------
// function ShipGeometry::setPositionAttitude
// -----------------------------------------------------------------------------
/// controls ship's location and orientation
// -----------------------------------------------------------------------------
void ShipGeometry::setPositionOrientation(const mml::Vector3d& a_xyz, ///< centerpoint location
                                          const mml::Vector3d& a_orientation) ///< roll, pitch, yaw
{
    ASSERT(!m_originApp, "only for !originApp");

    super::setPositionOrientation(a_xyz, a_orientation);

    m_earthPanels.clear();
    const xxship::Panels& shipPanels = getShipPanels();
    int n = shipPanels.getNumberOfPanels();
    for (int i = 0; i < n; ++i)
    {
        xxship::Panel earthPanel;
        const xxship::Panel& panel = shipPanels.getPanel(i);
        int nn = panel.getNumberOfVertices();
        for (int j = 0; j < nn; ++j)
        {
            mml::Vector3d vertex = panel.getVertex(j);
            vertex = m_attitude * vertex;
            vertex += m_position;
            earthPanel.addVertex(vertex);
        }
        earthPanel.flatten(); // computes centroid and normal
        m_earthPanels.addPanel(earthPanel);
    }
}


// -----------------------------------------------------------------------------
// function ShipGeometry::setPositionAttitude
// -----------------------------------------------------------------------------
/// controls ship's location and orientation
// -----------------------------------------------------------------------------
void ShipGeometry::setPositionAttitude(const mml::Vector3d& a_position, const mml::Matrix3d& a_attitude)
{
    ASSERT(m_originApp, "only for originApp");
    // TODO m_orientation is not set  yet
    m_orientation = mml::Vector3d(0, 0, 0);
    m_position = a_position;
    m_attitude = a_attitude;
    m_earthPanels = m_appPanels;
    m_earthPanels.rotate(a_attitude);
    m_earthPanels.translate(a_position);
}


// -----------------------------------------------------------------------------
// function ShipGeometry::getHorizontalBoundingBox
// -----------------------------------------------------------------------------
/// returns bounding box as seen from above
// -----------------------------------------------------------------------------
void ShipGeometry::getHorizontalBoundingBox(double* pXmin, double* pYmin,
                                            double* pXmax, double* pYmax) const
{
    m_earthPanels.getBoundingBox(pXmin, pYmin, pXmax, pYmax);
}


// -----------------------------------------------------------------------------
// function ShipGeometry::isOriginApp()
// -----------------------------------------------------------------------------
/// is origin at APP (or amidships)
// -----------------------------------------------------------------------------
bool ShipGeometry::isOriginApp() const
{
    return m_originApp;
}

// -----------------------------------------------------------------------------
// function setOriginApp
// -----------------------------------------------------------------------------
/// set flag for origin at APP
// -----------------------------------------------------------------------------
void ShipGeometry::setOriginApp(bool yes)
{
    m_originApp = yes;
}

// -----------------------------------------------------------------------------
// function ShipGeometry::translateAmidships
// -----------------------------------------------------------------------------
// move ship to new origin (amidships at still water level)
// -----------------------------------------------------------------------------
void ShipGeometry::translateAmidships()
{
    mst::Matrix4d ro;
    ro.makeEuler(0, atan((getDrafAft() - getDraftFore()) / getLPP()), 0);
    mst::Matrix4d tr;
    tr.makeTrans(-0.5 * getLPP(), 0.0, -(getDrafAft() + getDraftFore()) / 2);
    mst::Matrix4d affine = tr * ro;

    // translate panels to origin amidships at water level
    m_shipPanels.clear();
    const xxship::Panels& shipPanels = getAppPanels();
    int n = shipPanels.getNumberOfPanels();
    for (int i = 0; i < n; ++i)
    {
        xxship::Panel shipPanel;
        const xxship::Panel& panel = shipPanels.getPanel(i);
        int nn = panel.getNumberOfVertices();
        for (int j = 0; j < nn; ++j)
        {
            mml::Vector3d vertex = panel.getVertex(j);
            mst::Vector3d v(vertex(0), vertex(1), vertex(2));
            v *= affine;
            vertex = mml::Vector3d(v[0], v[1], v[2]);
            shipPanel.addVertex(vertex);
        }
        shipPanel.flatten(); // computes centroid and normal
        m_shipPanels.addPanel(shipPanel);
    }

    m_earthPanels = m_shipPanels;
}
